In [ ]:
epochs = 10
n_test_batches = 200

Part 11 - プライバシーに配慮したディープラーニングで分類問題を解く

データの機密性は重要です。と同時に、モデルの機密性も重要です

データは機械学習の肝です。組織はデータを作成したり集めたりすることで、独自のモデルをトレーニングすることができ、それをサービス(MLaaS)として外部に公開できます。自分たちでモデルのトレーニングを行えない組織は、公開されたサービスを使って自分たちのデータを推論することができます。

しかし、クラウド上のモデルにはプライバシーや知財の問題があります。外部の組織が使おうと思うと、推論したいデータをクラウドにアップロードするか、もしくはモデルをダウンロードする必要があります。入力データのアップロードにはプライバシーの問題がありますし、モデルのダウンロードはモデル所有者が知財を失ってしまうリスクがあります。

暗号化されたデータを使ってのコンピューテーション

こういった状況下における潜在的な解決策は、データとモデルの両方を暗号化し、お互いに知財を非公開とする事です。それを可能にする暗号化手法はいくつか存在します。その中でも、Secure Multi-Party Computation (SMPC)とHomomorphic Encryption (FHE/SHE) 、それに Functional Encryption (FE)はよく知られています。ここでは"Secure Multi-Party Computation" (introduced in detail here in tutorial 5)について扱います。"Secure Multi-Party Computation"はsharesを使って暗号化を行う手法でSecureNNやSPDZと呼ばれるライブラリを使用します。詳細はこちらのブログにてご確認ください。

これらのプロトコルは、暗号化されたデータを使ってのコンピューテーションにおいて、目覚ましい成果を上げています。私たちはこれらのプロトコルを開発者が個々に実装することなく(場合によっては裏で動いている暗号技術を意識することもなく)使える仕組みを開発しています。それでは、始めましょう。

セットアップ

このチュートリアルに必要な設定は次の通りです。データは手元にあると仮定してください。まず、手元にあるデータを使ってプライバシーに配慮したディープラーニングの手法を使ってモデルの定義とトレーニングを行います。次に何らかのデータを保持していて、モデルを使いたいユーザーと連携します。ここではモデルをトレーニングして公開する主体をサーバー(このケースではあなた)、モデルを使いたいユーザーをクライアントと呼ぶことにします。

サーバー(あなた)はモデルを暗号化し、クライアントはデータを暗号化します。あなたとクライアントはどちらも暗号化されたモデルとデータを使ってデータの分類を行います。その後、推論結果を暗号化された状態のままクライアントへ戻します。その際、サーバーはデータについて一切知ることはありません。(入力データ、推論結果のどちらに関してもです。)

理想的にはclientserversharesをもつべきですが、今回のケースでは簡単のため、sharesはBobとAliceという2つのリモートワーカーに分配します。もし、aliceはクラアントに、Bobはサーバーに属すと仮定すれば、正にサーバーとクライアントでsharesを分け合っている状態です。

この手法は、悪意の無い関係者間で、安全なコンピューテーションを実現できます。想定する環境はmany MPC frameworksにて標準化されています。 ここで言う悪意の無い関係者とは、データがそのまま(閲覧可能な状態で)送られてきたら見てしまうかもしれないけれど、基本的には正直で悪意のない関係者(サーバー、クライアント)という意味です。

準備はと問いました。早速見ていきましょう

Author:

ライブラリのインポート


In [ ]:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

PySyft関連のライブラリをインポートします。何名かのリモートワーカー(ここでは clientbob、それに aliceの3名です)と暗号化技術のプリミティブを提供するcrypto_providerを作成します。暗号化技術の基本データ型の詳細についてはSee our tutorial on SMPC for more detailsを参照してください。


In [ ]:
import syft as sy
hook = sy.TorchHook(torch) 
client = sy.VirtualWorker(hook, id="client")
bob = sy.VirtualWorker(hook, id="bob")
alice = sy.VirtualWorker(hook, id="alice")
crypto_provider = sy.VirtualWorker(hook, id="crypto_provider")

ここで、トレーニングで使用するハイパーパラメータを定義します。


In [ ]:
class Arguments():
    def __init__(self):
        self.batch_size = 64
        self.test_batch_size = 50
        self.epochs = epochs
        self.lr = 0.001
        self.log_interval = 100

args = Arguments()

データの準備

今回の設定では、サーバーがモデルと学習データを保持していると仮定しています。今回扱うデータはMNISTです。


In [ ]:
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=args.batch_size, shuffle=True)

次に、クライアントは、サーバーが提供するモデルを使って推論を行いたい、何らかのデータを持っていると仮定しているので、その準備をします。クライアントはsharesalicebobに分割することでデータを暗号化します。

SMPCは整数で動く暗号化プロトコルを使います。PySyftのtensor拡張機能、.fix_precision()を使って不動小数から整数へ変換を行います。例えば、精度を2とすると、0.123は小数点第2位以下が丸められ、12になります。


In [ ]:
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data', train=False,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=args.test_batch_size, shuffle=True)

private_test_loader = []
for data, target in test_loader:
    private_test_loader.append((
        data.fix_precision().share(alice, bob, crypto_provider=crypto_provider),
        target.fix_precision().share(alice, bob, crypto_provider=crypto_provider)
    ))

モデルの定義

"Feed Forward"だけからなる基本的なモデルを定義します。このモデルはサーバーによって定義されます。


In [ ]:
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784, 500)
        self.fc2 = nn.Linear(500, 10)

    def forward(self, x):
        x = x.view(-1, 784)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        return x

トレーニングループを定義

この学習はサーバーのローカル環境下で行われます。ごく普通のPyTorchのトレーニングです。


In [ ]:
def train(args, model, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()
        output = model(data)
        output = F.log_softmax(output, dim=1)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % args.log_interval == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * args.batch_size, len(train_loader) * args.batch_size,
                100. * batch_idx / len(train_loader), loss.item()))

In [ ]:
model = Net()
optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)

for epoch in range(1, args.epochs + 1):
    train(args, model, train_loader, optimizer, epoch)

In [ ]:
def test(args, model, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            output = model(data)
            output = F.log_softmax(output, dim=1)
            test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss
            pred = output.argmax(1, keepdim=True) # get the index of the max log-probability 
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)

    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))

In [ ]:
test(args, model, test_loader)

モデルの学習が完了しました。準備OKです。

暗号化されたデータとモデルを使っての評価

それでは、クライアントがクライアントのデータに対して推論を行えるよう、モデルをクライアントへ送りましょう。ですが、このモデルはデリケートな情報を含むため(トレーニングで時間と労力がかかっています!)、そのウェイトは非公開にしたいですよね。ここまでのチュートリアルで暗号化されたデータをリモートワーカーへ送ったように。


In [ ]:
model.fix_precision().share(alice, bob, crypto_provider=crypto_provider)

このテスト関数は暗号化されたデータを使ってのテストができる関数です。モデルのウェイト、入力データ、推論結果、そして正解データは全て暗号化されています。

ですが、構文はピュアなPyTorchとほとんど同じですね。

唯一サーバー側で複合化するのは最終的な精度のスコアだけです。スコアは推論結果を評価するために必要です。


In [ ]:
def test(args, model, test_loader):
    model.eval()
    n_correct_priv = 0
    n_total = 0
    with torch.no_grad():
        for data, target in test_loader[:n_test_batches]:
            output = model(data)
            pred = output.argmax(dim=1) 
            n_correct_priv += pred.eq(target.view_as(pred)).sum()
            n_total += args.test_batch_size
            # このテスト関数は暗号化されたデータでの評価を行えます。モデルのウェイト(パラメータ)、入力データ、推論結果、そ
            # して正解ラベルと全てが暗号されています。
            
            # しかしながら、みなさんお気づきの通り、ごくごく一般的なPyTorchのテストスクリプトとほとんど同じです。
            
            # 唯一複合化しているのは、200アイテムのバッチ事に計算している精度確認のためのすこだけです。
            # この数字を見ることで学習されたモデルの性能が良いのか悪いのか評価できます。
            
            n_correct = n_correct_priv.copy().get().float_precision().long().item()
    
            print('Test set: Accuracy: {}/{} ({:.0f}%)'.format(
                n_correct, n_total,
                100. * n_correct / n_total))

In [ ]:
test(args, model, private_test_loader)

ジャジャーン!今回は暗号化されたデータを使っての推論処理に関する一通りのプロセスを学習しました。モデルのウェイトはクライアント側からは見えませんし、クライアントの入力データや推論結果もサーバー側からは見えません。

パフォーマンスに関してですが、1枚の画像の分類に掛かる時間は0.1秒以下です。私のノートブック(2.7 GHz Intel Core i7, 16GB RAM)でざっと33ミリ秒といったところでしょうか。ですが、今回のチュートリアルでは全てのワーカーが実際には私のマシン上にいるため、通信に時間が掛かっていません。実際の環境でそれぞれのワーカーが別々の場所に存在する場合は、ワーカー間の通信速度に大きく影響を受けます。

Conclusion

今回のチュートリアルでは、PyTorchとPySyftを使うことで、暗号化技術の専門家でなくても、機密データを使った、実践的、かつセキュアなディープラーニングが簡単に実行できることを学びました。

本トピックについてはより多くの事例が追加されていく予定です。畳み込み層を使ったニューラルネットワークや、他のライブラリとのパフォーマンス比較や、外部にある機密データを扱ってのトレーニングなどなどです。お楽しみに。

もし、このチュートリアルを気に入って、プライバシーに配慮した非中央集権的なAI技術や付随する(データやモデルの)サプライチェーンにご興味があって、プロジェクトに参加したいと思われるなら、以下の方法で可能です。

PySyftのGitHubレポジトリにスターをつける

一番簡単に貢献できる方法はこのGitHubのレポジトリにスターを付けていただくことです。スターが増えると露出が増え、より多くのデベロッパーにこのクールな技術の事を知って貰えます。

Slackに入る

最新の開発状況のトラッキングする一番良い方法はSlackに入ることです。 下記フォームから入る事ができます。 http://slack.openmined.org

コードプロジェクトに参加する

コミュニティに貢献する一番良い方法はソースコードのコントリビューターになることです。PySyftのGitHubへアクセスしてIssueのページを開き、"Projects"で検索してみてください。参加し得るプロジェクトの状況を把握することができます。また、"good first issue"とマークされているIssueを探す事でミニプロジェクトを探すこともできます。

寄付

もし、ソースコードで貢献できるほどの時間は取れないけど、是非何かサポートしたいという場合は、寄付をしていただくことも可能です。寄附金の全ては、ハッカソンやミートアップの開催といった、コミュニティ運営経費として利用されます。

OpenMined's Open Collective Page


In [ ]:


In [ ]: